using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.DataAccess.SPI;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.FAS.Runtime.StateMachine.Service;
using T.FAS.Runtime.Metadata.Client;
using T.FAS.Runtime.Metadata.SPI;
//using T.FAS.Authority.MetaData.Dev.Plugin;
//using T.FAS.Authority.Metadata.Entity;
using T.FAS.Runtime.DataAccess.Kernel;
using T.FAS.Runtime.DataAccess.Common;
using UserUtil = T.FAS.Runtime.DataAccess.Common.UserUtil;

namespace T.FAS.Runtime.DataAccess.Engine
{
    /// <summary>
    /// 统一数据访问服务状态机插件
    /// </summary>
    public class AuthorityPlugin : IDataAccessPlugin
    {
        #region Fields & Properties

        private readonly IDACLogService _localLogService = DACLogFactory.GetDACLogService();
        private IMetaDataClientService metaDataClientService = MetaDataClientService.GetInstance();
        private const string cdoAuthConst = "CDOAuth";
        private const string pluginName = "权限插件";
        public string PluginCode => "AuthorityPlugin";
        public bool IsCanRepeatExecute => false;

        //private ActionPermissionDefinitionManager permissionManager = new ActionPermissionDefinitionManager();
        //private IDataPermissionsService permissionsService = DataPermissionsService.GetInstance();

        /// <summary>
        /// 插件注入点
        /// </summary>
        public int InjectionPoint
        {
            get
            {
                var injectionPoint = SPI.InjectionPoint.Entry
                                    | SPI.InjectionPoint.BeforeBuildSQLDOM;
                return (int)injectionPoint;
            }
        }

        /// <summary>
        /// 插件注入方法类型
        /// </summary>
        public int InjectionMethodType
        {
            get
            {
                var injectionType = SPI.InjectionMethodType.Create
                                    | SPI.InjectionMethodType.Retrieve
                                    | SPI.InjectionMethodType.Update
                                    | SPI.InjectionMethodType.Delete;
                return (int)injectionType;
            }
        }

        public List<string> MetaType => new List<string> { DataModelConst.CDOMetaType, DataModelConst.DQMetaType };


        public InvokeContext invokeContext;

        public InvokeContext InvokeContext
        {
            get
            {
                return invokeContext;
            }
        }

        #endregion

        #region Public Funcs
        public void BuildInvokeContext(InvokeContext invokeContext)
        {
            this.invokeContext = invokeContext;
        }

        public ActionResult Execute(InjectionMethodType InjectMethodType, InjectionPoint InjectPoint, DataAccessPluginExtParam extExecuteData = null)
        {
            if (ValidateInjectPoint(InjectMethodType, InjectPoint))
                return Execute(invokeContext, InjectMethodType);
            else
                return new ActionResult
                {
                    Result = true,
                    Message = "不符合当前插件执行时机， 不予执行",
                };
        }

        #endregion

        #region Private Funcs
        /// <summary>
        /// 
        /// </summary>
        /// <param name="invokeContext"></param>
        private ActionResult Execute(InvokeContext invokeContext, InjectionMethodType injectionMethodType)
        {
            if (invokeContext == null)
            {
                return new ActionResult
                {
                    PluginCode = PluginCode,
                    Result = false,   //插件类型不匹配时，不执行插件逻辑
                    Message = $"invokeContext未初始化，请先执行初始化方法",
                };
            }
            if (!this.IsCanRepeatExecute && invokeContext.ExecuteLogs.Count > 0)
                return new ActionResult
                {
                    PluginCode = PluginCode,
                    Result = false,   //插件类型不匹配时，不执行插件逻辑
                    Message = $"{pluginName}插件不允许重复执行",
                };
            try
            {
                if (!MetaType.Contains(invokeContext.MetaType))
                    return new ActionResult
                    {
                        Result = true,   //插件类型不匹配时，不执行插件逻辑
                        PluginCode = PluginCode,
                    };
                var flag = ValidateContext(invokeContext, out ActionResult actionResult);
                if (!flag)
                    return actionResult;
                try
                {
                    /****************************权限校验****************************/
                    ActionResult ExecuteResult = new ActionResult();
                    /****************************DQ权限校验****************************/
                    if (invokeContext.MetaType == DataModelConst.DQMetaType)
                    {
                        var meta = invokeContext.SourceMeta as DataQuery;
                        if (meta.IsPermissionControl)
                            ExecuteResult = DQAuthority(invokeContext);
                        else
                            ExecuteResult = actionResult;

                    }
                    /****************************CDO权限校验****************************/
                    else if (invokeContext.MetaType == DataModelConst.CDOMetaType)
                    {
                        var meta = invokeContext.SourceMeta as CompositeDataObject;
                        //只校验
                        if (injectionMethodType == SPI.InjectionMethodType.Delete || injectionMethodType == SPI.InjectionMethodType.Update)
                            ExecuteResult = CDOAuthority(invokeContext, injectionMethodType);
                    }

                    if (ExecuteResult.Result)
                        invokeContext.ExecuteLogs.Add(ExecuteResult);
                    return ExecuteResult;
                }
                catch (Exception e)
                {
                    throw new FasException(DataAccessErrorCode.PlugIn.Authority.Error, string.Format(CommonStringClassEngine.AuthorityPlugin_Execute, pluginName), e);
                }
            }
            catch (Exception e)
            {
                ActionResult actionResult = new ActionResult
                {
                    Result = false,
                    Exception = e,
                    Message = $"{pluginName}执行异常, Msg:{e.Message}",
                    ActionErrorHandleType = ActionErrorHandleType.Throw,
                };
                return actionResult;
            }
        }

        /// <summary>
        /// 运行时对接权限，获取权限sql集合
        /// </summary>
        /// <param name="invokeContext"></param>
        /// <returns></returns>
        private ActionResult DQAuthority(InvokeContext invokeContext)
        {
            //var permissionData = GetDQAuthRuntimeParams(invokeContext.SourceMeta.MetaDataID);
            List<string> authSqls = new List<string>();
            //try
            //{
            //    foreach (var permission in permissionData)
            //    {
            //        var sql = permissionsService.GetFilterSqlByUserId(UserUtil.GetCurrentUserID(), permission.FunctionId, permission.DpdId);
            //        authSqls.Add(sql);
            //    }
            //}
            //catch (Exception e)
            //{

            //    throw new FasException(DataAccessErrorCode.PlugIn.Authority.Error, string.Format(CommonStringClassEngine.AuthorityPlugin_GetAuthSQLFail, pluginName, e.Message), e);
            //}
            var ExecuteResult = new ActionResult
            {
                Result = true,
                InvokeContext = invokeContext,
                ResultData = new Dictionary<string, object>
                {
                      { "AuthSQL",authSqls }
                },
            };
            return ExecuteResult;
        }

        /// <summary>
        /// CDO对接权限管理
        /// </summary>
        /// <param name="invokeContext"></param>
        /// <returns></returns>
        private ActionResult CDOAuthority(InvokeContext invokeContext, InjectionMethodType injectionMethodType)
        {
            if (invokeContext.ExtProperties == null || !invokeContext.ExtProperties.ContainsKey(cdoAuthConst))
                return new ActionResult { Result = true, Message = "未开启权限校验" };

            //var permissionData = invokeContext.ExtProperties[cdoAuthConst] as List<AuthRuntimeParam>;
            //if (permissionData == null || permissionData.Count == 0)
            //    return new ActionResult { Result = true, Message = "权限条件为空" };

            //var cdo = invokeContext.SourceMeta as CompositeDataObject;
            //List<string> authSqls = new List<string>();
            //try
            //{
            //    foreach (var permission in permissionData)
            //    {
            //        var sql = permissionsService.GetFilterSqlByUserId(UserUtil.GetCurrentUserID(), permission.FunctionId, permission.DpdId);
            //        authSqls.Add(sql);
            //    }

            //    _localLogService.Write($"CDO:{cdo.Code}开启权限管理，获取的权限SQL内容为:\r\n{string.Join("\r\n", authSqls)}\r\n");
            //    //因为权限系统获取sql都带着with(nolock)，需要根据CDO的数据库类型进行替换
            //    authSqls = ProcessAuthSqls(invokeContext, authSqls);
            //    _localLogService.Write($"CDO:{cdo.Code}开启权限管理，替换后的权限SQL内容为:\r\n{string.Join("\r\n", authSqls)}\r\n");
            //}
            //catch (Exception e)
            //{

            //    throw new FasException(DataAccessErrorCode.PlugIn.Authority.Error, string.Format(CommonStringClassEngine.AuthorityPlugin_GetAuthSQLFail, pluginName, e.Message), e);
            //}
            //foreach (var sql in authSqls)
            //{
            //    if (CheckPermission(invokeContext, sql, injectionMethodType))
            //    {
            //        var msg = $"cdo:{cdo.Code}执行权限校验失败<br />生成权限sql:{sql}<br />";
            //        return new ActionResult
            //        {
            //            Result = false,
            //            Message = msg,
            //            ActionErrorHandleType = ActionErrorHandleType.Throw,
            //            Exception = new FasException(DataAccessErrorCode.PlugIn.Authority.PermissionError, msg),
            //        };
            //    }
            //}
            return new ActionResult
            {
                Result = true,
                Message = "权限校验成功",
            };
        }

        /// <summary>
        /// 校验权限
        /// </summary>
        /// <param name="invokeContext"></param>
        /// <param name="authSql"></param>
        /// <returns></returns>
        private bool CheckPermission(InvokeContext invokeContext, string authSql, InjectionMethodType injectionMethodType)
        {
            string sql = default;
            string configKey = default;
            CompositeDataObject cdo = invokeContext?.SourceMeta as CompositeDataObject;
            try
            {
                var cno = cdo.RootCompositeNodeObject;
                var tableName = cno.DataObject.LogicTableName ?? cno.DataObject.ActualTableNames;
                var tableAlias = cno.DataObjectTableAlias;
                configKey = cno.DataObject.Database.DatabaseConfigs.FirstOrDefault(cfg => cfg.IsDefault)?.ConfigKey;
                var db = DatabaseFactory.CreateDefaultDatabase().GetDatabaseByConfigkey(configKey);
                AuthSqlContext authSqlContext = new AuthSqlContext
                {
                    TableName = tableName,
                    TableAlias = tableAlias,
                    PkColumn = cno.GetPKColumn().ColumnName,
                };
                if (injectionMethodType == SPI.InjectionMethodType.Delete
                    || injectionMethodType == SPI.InjectionMethodType.Retrieve
                    )
                {
                    authSqlContext.IDs = string.IsNullOrEmpty(invokeContext.SourceDataID)
                                        ? invokeContext.SourceDataIDs
                                        : new List<string> { invokeContext.SourceDataID };
                }
                else
                {
                    authSqlContext.Record = invokeContext.SourceData;
                }

                var pksql = GetPkSql(authSqlContext, injectionMethodType);
                sql = $"select count(1) from {tableName} {tableAlias} where {pksql} and {authSql} ";
                var result = db.ExecuteScalar(sql)?.ToString();
                if (int.TryParse(result, out int cnt))
                {
                    return cnt > 0;
                }
                return false;
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.PlugIn.Authority.Error, string.Format(CommonStringClassEngine.AuthorityPlugin_CheckPermission, cdo.Code, configKey, sql, e.Message), e);
            }
        }

        /// <summary>
        /// 获取主键条件
        /// </summary>
        /// <param name="invokeContext"></param>
        /// <param name="injectionMethodType"></param>
        /// <returns></returns>
        private string GetPkSql(AuthSqlContext invokeContext, InjectionMethodType injectionMethodType)
        {
            switch (injectionMethodType)
            {
                case SPI.InjectionMethodType.Create:
                    return null;
                case SPI.InjectionMethodType.Retrieve:
                    return $"{invokeContext.TableAlias}.{invokeContext.PkColumn}='{invokeContext.IDs.FirstOrDefault()}'";
                case SPI.InjectionMethodType.Update:
                    var pk = invokeContext.Record.GetValue(invokeContext.PkColumn).ToString();
                    return $"{invokeContext.TableAlias}.{invokeContext.PkColumn}='{pk}'";
                case SPI.InjectionMethodType.Delete:
                    return $"{invokeContext.TableAlias}.{invokeContext.PkColumn} in ('{string.Join("','", invokeContext.IDs)}')";
                default:
                    return default;
            }
        }

        /// <summary>
        /// 校验上下文变量正确性
        /// </summary>
        /// <param name="invokeContext"></param>
        /// <param name="errorResult"></param>
        /// <returns></returns>
        private bool ValidateContext(InvokeContext invokeContext, out ActionResult errorResult)
        {
            errorResult = new ActionResult
            {
                PluginCode = PluginCode,
                Result = true,
                InvokeContext = invokeContext,
                ActionErrorHandleType = ActionErrorHandleType.Throw,
            };
            if (invokeContext == null)
            {
                errorResult.Result = false;
                errorResult.Message = "状态机插件执行异常,invokeContext参数为空";
            }
            return errorResult.Result;
        }

        /// <summary>
        /// 运行时获取权限信息
        /// </summary>
        /// <param name="cdo"></param>
        /// <returns></returns>
        //private List<AuthRuntimeParam> GetDQAuthRuntimeParams(string DataQueryID)
        //{
        //    try
        //    {
        //        return permissionManager.GetRuntimeParam(DataQueryID);
        //    }
        //    catch (Exception e)
        //    {

        //        throw new FasException(DataAccessErrorCode.PlugIn.Authority.Error, string.Format(CommonStringClassEngine.AuthorityPlugin_GetDQAuthRuntimeParams, pluginName, DataQueryID), e);
        //    }
        //}

        /// <summary>
        /// 校验注入点
        /// </summary>
        /// <param name="MethodType"></param>
        /// <param name="Point"></param>
        /// <returns></returns>
        private bool ValidateInjectPoint(InjectionMethodType MethodType, InjectionPoint Point)
        {
            return (this.InjectionMethodType & (int)MethodType) == (int)MethodType
                      && (this.InjectionPoint & (int)Point) == (int)Point;

        }

        private List<string> ProcessAuthSqls(InvokeContext invokeContext, List<string> authSQLs)
        {
            if (authSQLs == null || authSQLs.Count == 0) return authSQLs;
            var cdo = invokeContext.SourceMeta as CompositeDataObject;
            if (cdo == null) return authSQLs;
            string dbType = cdo.RootCompositeNodeObject?.DataObject?.Database?.DbType;
            DatabaseType databaseType;
            if (string.IsNullOrEmpty(dbType))
                databaseType = DatabaseType.SQLServer;
            else
                databaseType = (DatabaseType)Enum.Parse(typeof(DatabaseType), dbType);

            List<string> result = new List<string>();
            foreach (var sql in authSQLs)
            {
                if (databaseType == DatabaseType.MySQL || databaseType == DatabaseType.PostgreSQL)
                {
                    result.Add(sql.Replace("with(nolock)", ""));
                }
                else
                {
                    result.Add(sql);
                }
            }
            return result;
        }
        #endregion
    }

    #region Class

    class AuthSqlContext
    {
        public string TableName { get; set; }
        public string TableAlias { get; set; }
        public string PkColumn { get; set; }
        public TRecord Record { get; set; }
        public List<string> IDs { get; set; }
    }
    #endregion
}
